package com.fortune.cat.filter;

import com.fortune.cat.Exception.CustomizeException;
import io.netty.handler.codec.http.HttpScheme;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.Duration;

import static com.fortune.cat.constant.ServerWebExchangeConstants.GATEWAY_REQUEST_BODY_STR;
import static com.fortune.cat.filter.CustomizeFilterOrder.GLOBAL_LOG_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

/**
 * @author zhaozeyang
 */
@Slf4j
@Component
public class GlobalLogFilter implements GlobalFilter, Ordered {


    @Value("${xes.globaLog.timeout:10000ms}")
    private Duration timeout;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        ServerHttpRequest request = exchange.getRequest();
        //设置traceId
//        if (Objects.nonNull(tracing)) {
//            Span span = tracing.tracer().currentSpan();
//            exchange.getResponse().getHeaders().add("XES-GATEWAY-X-B3-TraceId", span.context().traceIdString());
//        }
        //只记录http&https的请求
        String scheme = request.getURI().getScheme();
        if ((!HttpScheme.HTTP.name().toString().equals(scheme) && !HttpScheme.HTTPS.name().toString().equals(scheme))) {
            return chain.filter(exchange);
        }
        String method = request.getMethod().name();
        //包装原始响应
        BodyHandlerServerHttpResponseDecorator responseDecorator = new BodyHandlerServerHttpResponseDecorator(
                initBodyHandler(exchange, startTime), exchange.getResponse());
        ServerRequest serverRequest = new DefaultServerRequest(exchange);
        //根据content-length决定是否打印请求body
        if (request.getHeaders().getContentLength() <= 0) {
            logRequest(request.getURI(), method, request.getHeaders(), null);
            return chain.filter(exchange.mutate().request(request).response(responseDecorator).build());
        }
        return serverRequest.bodyToMono(String.class)
                .timeout(timeout)
                .onErrorResume(ex -> Mono.empty())
                //当content-length伪造，body为空，则不会进行此方法
                .flatMap(reqBody -> {
                    //重写原始请求
                    ServerHttpRequestDecorator decorator = packageRequest(exchange, reqBody);
                    //并将body字符写入exchange，为之后获取requestBody提供便利
                    exchange.getAttributes().put(GATEWAY_REQUEST_BODY_STR, reqBody);
                    //打印原始请求日志
                    logRequest(serverRequest.uri(), serverRequest.method().name(), decorator.getHeaders(), reqBody);
                    return chain.filter(exchange.mutate().request(decorator).response(responseDecorator).build());
                }).then(Mono.defer(() -> {
                    //此处为防止content-length伪造等原因造成的读取超时
                    if (exchange.getResponse().isCommitted()) {
                        return Mono.empty();
                    }
                    //应该给予异常提醒
                    throw CustomizeException.createCustomizeException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_GATEWAY.value(),
                            "content length gt 0 but have no body");
                }));
    }

    private void logRequest(URI uri, String method, HttpHeaders headers, String reqBody) {
        log.info("GlobalLogFilter | request uri is {}, method is {}", uri, method);
        log.info("GlobalLogFilter | request headers is {}", headers);
        log.info("GlobalLogFilter | request body is {}", reqBody);
    }


    private ServerHttpRequestDecorator packageRequest(ServerWebExchange exchange, String body) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                if (StringUtils.isBlank(body)) {
                    return Flux.empty();
                }
                return Flux.just(body).map(bx -> exchange.getResponse().bufferFactory().wrap(bx.getBytes()));
            }
        };
    }

    @Override
    public int getOrder() {
        //在NettyWriteResponseFilter之前
        return GLOBAL_LOG_ORDER;
    }

    /**
     * 响应body处理，添加响应的打印
     *
     * @param exchange
     * @param startTime
     * @return
     */
    protected BodyHandlerFunction initBodyHandler(ServerWebExchange exchange, long startTime) {
        return (resp, body) -> {
            //拦截
            MediaType originalResponseContentType = MediaType.valueOf(exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR));
            HttpHeaders httpHeaders = exchange.getResponse().getHeaders();
            httpHeaders.setContentType(originalResponseContentType);
            DefaultClientResponseAdapter clientResponseAdapter = new DefaultClientResponseAdapter(body, httpHeaders);
            Mono<String> bodyMono = clientResponseAdapter.bodyToMono(String.class);
            return bodyMono.flatMap((respBody) -> {
                //打印返回响应日志
                logResponse(System.currentTimeMillis() - startTime, resp.getStatusCode(), resp.getHeaders(), respBody);
                return resp.writeWith(Flux.just(respBody).map(bx -> resp.bufferFactory().wrap(bx.getBytes())));
            }).then();
        };
    }

    private void logResponse(long ct, HttpStatus statusCode, HttpHeaders headers, String respBody) {
        log.info("GlobalLogFilter | response ct is {}, statusCode is {}", ct, statusCode);
        log.info("GlobalLogFilter | response headers is {}", headers);
        log.info("GlobalLogFilter | response body is {}", respBody);
    }
}